#pragma once
#include "const.h"
#include <thread>
#include <jdbc/mysql_driver.h>
#include <jdbc/mysql_connection.h>
#include <jdbc/cppconn/prepared_statement.h>
#include <jdbc/cppconn/resultset.h>
#include <jdbc/cppconn/statement.h>
#include <jdbc/cppconn/exception.h>
#include <queue>

/**
 * 数据库连接池
 * 提供与业务强相关的数据业务操作DAO层
 */


/**
 * 对于池：线程池，连接池
 * 1.池资源有两种：1.可以共享 2.不可以共享   前者只要不对容器写，就不用进行上锁  后者，直接对容器上锁，把资源从容器去除即可    或者也可以对容器每一个资源使用一把锁，但是消耗太大
 * 2.线程池：如果结束时队列里还有任务怎么办？ 1.不执行，直接结束。2.执行完再退出。  所有线程都是在对任务队列进行消费，会检查是否为空，如果为空，就会挂起自己
 * 等待生产者信号通知，从而继续执行
 * 然而线程要退出，不能想pthread那样直接发送信号让他结束，所以通过STOP信号让线程自己正常结束，所以生产者信号有哪些：任务提交，池停止时的信号
 * Stop---->通知所有被挂起的消费者线程执行notice_all(),不再被堵塞，而是持续不断地尝试获得锁----->最终所有阻塞的消费者线程全都退出
 * 正在进行的线程如何退出？
 * 正在执行的线程会在执行完当前的任务之后，返回继续取数据的时候，发现已经结束了，就会退出。
 * 那么这样，如果任务队列里还有任务，就不会执行了
 * 
 * 如何避免这种情况：任务队列存在任务，但是所有线程池通知关闭，完成所有任务后才退出
 * 
 * 1.由于还有任务，所以消费者线程绝对不会被“无任务”挂起，只会由于线程停止被挂起
 * 而且此时由于可能存在生产者继续投递任务，可能还会被唤醒，但是依然由于“线程池停止”而被唤醒，如果这些线程不退出，继续执行里面的任务
 * 那么线程池永远不会关闭，由于可能存在更多的任务加入，所以这些被卡在“无任务”或者“线程池停止”的线程，必须停止，所以里面的逻辑没有错
 * 由于线程池停止的，影噶结束，这就结束掉一部分的线程
 * 
 * 不被阻塞的情况：1.线程池停止  或者  2.任务队列不为空    现在任务队列一定不空，所以一定不会被阻塞，但是由于线程池停止的信息
 * 所有的正在进行的线程都会最终结束
 * 
 * 那么如何保证所有的已经加入到任务队列里的任务都被结束呢？
 * 
 * 
 * 有一个思想，就是生产者每加入一个任务，就++
 * 消费者每完成一个任务，就--
 * 
 * 最终退出线程池时，应该是让整个任务队列的都退出才可以退出线程池
 * 
 * 
 * 
 * 保证只要加入到队列就不会不被执行，经过实践验证，一定会执行完所有已经加入到队列中的任务，，才会最终退出，不会中途退出的
 * 
 * 
 * 
 */
/**
 * noticy_one noticy_all的区别：前者只有一个线程会被唤醒，其余的不会唤醒。后者会唤醒所有的线程，但是之后不再被杜塞，而是不断铲射高hi获得锁，最终执行
 * 也就是说，前者会随即唤醒一个，其余的继续阻塞，后者则会唤醒所有人，条件变量失效，所有的人开始竞争，但就是竞争到了，也会由于无法跳出唤醒条件，从而继续“变相阻塞”
 * 前者，只会唤醒一个，后者唤醒所有的，且不再被条件变量阻塞，但是由于挑不出“虚假唤醒”，所以也不会出现翁提，但是会持续的占有资源，造成问题：何时使用。当你不再需要条件变量
 * 挂起时使用，因为此时之后，就不会被条件变量挂起了
 * 前者节省资源减少竞争，后者存在竞争，但是会使条件变量不再存在阻塞，不再需要再次唤醒，也就是条件变量失效。
 */


//数据库连接，连接交给智能指针进行管理，申请完成之后-->封装SqlConnection----->放入连接池管理
class SqlConnection {
public:
	SqlConnection(sql::Connection* con, int64_t lasttime):_con(con), _last_oper_time(lasttime){}
	std::unique_ptr<sql::Connection> _con;		//数据库连接
	int64_t _last_oper_time;					//检测心跳
};


//多线程使用，最好支持单例，继承单例就行
class MySqlPool {
public:

	//线程不安全，最好改成线程安全，上锁，而且有可能创建多个池，最好继承单例
	MySqlPool(const std::string& url, const std::string& user, const std::string& pass, const std::string& schema, int poolSize)
		: url_(url), user_(user), pass_(pass), schema_(schema), poolSize_(poolSize), b_stop_(false){
		try {
			for (int i = 0; i < poolSize_; ++i) {
				sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();
				//139.159.184.126
				auto*  con = driver->connect("139.159.184.126:3306", user_, pass_);
				con->setSchema(schema_);
				// 获取时间
				auto currentTime = std::chrono::system_clock::now().time_since_epoch();
				// 计算时间
				long long timestamp = std::chrono::duration_cast<std::chrono::seconds>(currentTime).count();
				pool_.push(std::make_unique<SqlConnection>(con, timestamp));
			}

			//开启检测线程
			_check_thread = 	std::thread([this]() {
				while (!b_stop_) {
					checkConnection();  
					std::this_thread::sleep_for(std::chrono::seconds(60));
				}
			});

			//检测线程分离，由于会对
			_check_thread.detach();
		}
		catch (sql::SQLException& e) {
			std::cout << "mysql pool init failed, error is " << e.what()<< std::endl;
		}
	}

	//对所有的连接进行一次心跳检查
	void checkConnection() 
	{
		//多个线程操作，上锁更新所有的sqlConnection时间
		std::lock_guard<std::mutex> guard(mutex_);
		int poolsize = pool_.size();
		// 获取时间信息
		auto currentTime = std::chrono::system_clock::now().time_since_epoch();
		long long timestamp = std::chrono::duration_cast<std::chrono::seconds>(currentTime).count();

		//把所有的连接POP出来
		for (int i = 0; i < poolsize; i++) {
			auto con = std::move(pool_.front());
			pool_.pop();

			//退出函数时执行
			Defer defer([this, &con]() {
				pool_.push(std::move(con));
			});

			//如果没有超时，继续
			if (timestamp - con->_last_oper_time < 5) {
				continue;
			}
			
			//如果超时了，重新连接下数据库，然后更新下最后时刻的时间，保证心跳，最后重新要入QUEUE
			try {
				std::unique_ptr<sql::Statement> stmt(con->_con->createStatement());
				stmt->executeQuery("SELECT 1");
				con->_last_oper_time = timestamp;
				//std::cout << "execute timer alive query , cur is " << timestamp << std::endl;
			}
			catch (sql::SQLException& e) {

				//如果发生异常，没有办法连接到数据库，说明已经超过数据库最长时间，那就重新连接下
				std::cout << "Error keeping connection alive: " << e.what() << std::endl;
				sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();
				auto* newcon = driver->connect(url_, user_, pass_);
				newcon->setSchema(schema_);
				con->_con.reset(newcon);
				con->_last_oper_time = timestamp;
			}

			//最终Defer对象析构，就会重新把对象压入Queeue,由于会对Queue修改，所以需要一把锁，保证数据安全，由于不确定时间的竞态条件，不要依赖于其
		}
	}

	//获得连接
	std::unique_ptr<SqlConnection> getConnection() {
		//上锁，由于connection不是共享资源，只要被其中一个人拿走了，就不会再被其他人共享，所以connecton本生是安全的，并不需要保护
		//所以锁要确定一个对象本生是不是被共享
		std::unique_lock<std::mutex> lock(mutex_);
		cond_.wait(lock, [this] { 
			if (b_stop_) {
				return true;
			}		
			return !pool_.empty(); });

		//
		if (b_stop_) {
			return nullptr;
		}
		std::unique_ptr<SqlConnection> con(std::move(pool_.front()));
		pool_.pop();
		return con;
	}

	//会不会存在，已经析构了池对象，然后某一个线程继续调用return，从而发生“空悬的”现象
	//操作pool，通过mgr进行，pool不直接进行外界交互
	//1.pool生命周期与mgr一致，由于构造函数私有
	//所以有没有办法？析构函数也私有？单例有一个智能指针
	//使用智能指针去保证单例，所以mgr是单例，那么里面的pool也一定是单例的，那么如何去释放资源呢？
	//那么只有mgr释放时，才会释放，而什么时候mgr释放呢？当没有任何人引用时才会释放，因为他是单例
	//
	void returnConnection(std::unique_ptr<SqlConnection> con) {
		std::unique_lock<std::mutex> lock(mutex_);
		if (b_stop_) {
			return;
		}
		pool_.push(std::move(con));
		cond_.notify_one();
	}

	void Close() {
		b_stop_ = true;
		cond_.notify_all();
	}

	~MySqlPool() {
		std::unique_lock<std::mutex> lock(mutex_);
		while (!pool_.empty()) {
			pool_.pop();
		}
	}

private:
	std::string url_;				//IP
	std::string user_;				//用户
	std::string pass_;				//密码
	std::string schema_;			//数据库，字符集
	int poolSize_;					//池子大小
	std::queue<std::unique_ptr<SqlConnection>> pool_;	//管理池的容器
	std::mutex mutex_;				//锁
	std::condition_variable cond_;	//条件变量，不可以共想连接
	std::atomic<bool> b_stop_;		//停止
	std::thread _check_thread;					//连接池监控线程，类似于线程池的检测线程
};



//结合业务具体的操作,用户信息
struct UserInfo {
	std::string name;
	std::string pwd;
	int uid;
	std::string email;
};

class MysqlDao
{
public:
	MysqlDao();
	~MysqlDao();
	int RegUser(const std::string& name, const std::string& email, const std::string& pwd);
	int RegUserTransaction(const std::string& name, const std::string& email, const std::string& pwd, const std::string& icon);
	bool CheckEmail(const std::string& name, const std::string & email);
	bool UpdatePwd(const std::string& name, const std::string& newpwd);
	bool CheckPwd(const std::string& name, const std::string& pwd, UserInfo& userInfo);
	bool TestProcedure(const std::string& email, int& uid, std::string& name);
private:
	std::unique_ptr<MySqlPool> pool_;
};


